<!--
通过JSON对象构建表单
@author pan
@createDate 2022/02 2022/2/28 12:43
-->
<script setup lang="ts" name="JsonForm">
import { getCurrentInstance, nextTick, reactive, ref } from 'vue'
import { ElForm, ValidateFieldCallback } from 'element-plus'
import { Callback } from 'element-plus/es/components/form/src/form.vue'
import { JsonFormItemType } from 'components/json-form/JsonForm.type'
import { extractCustomEvent, extractCustomProp } from './JsonFormUtil'

withDefaults(
  defineProps<{
    /**
     * el-form组件所有属性. 默认:空对象
     */
    elFormProp?: Record<string, any>
    /**
     * el-form组件所有事件. 默认:空对象
     */
    elFormEvent?: Record<string, any>
    /**
     * 表单数据. 默认:空对象
     */
    formData?: any
    /**
     * 表单项配置
     */
    itemArr?: JsonFormItemType[]
  }>(),
  {
    elFormProp: () => ({}),
    elFormEvent: () => ({}),
    formData: () => ({}),
    itemArr: () => [],
  }
)
type FormInstance = InstanceType<typeof ElForm>
const jsonFormRef = ref<FormInstance>()

/**
 * 获取JSON Form内部的el-form实例
 */
function getElFormInstance(): FormInstance | null {
  return jsonFormRef.value as FormInstance
}

/**
 * 执行表单验证
 * @param callback 验证之后的回调
 */
function validate(callback?: Callback): Promise<boolean> | undefined {
  return jsonFormRef.value?.validate(callback)
}

/**
 * 对整个表单进行重置，将所有字段值重置为初始值并移除校验结果
 */
function resetFields() {
  jsonFormRef.value?.resetFields()
}

/**
 * 清理指定字段的表单验证信息。 参数是一个或多个需要清除验证信息的表单属性名。 如果省略了参数，将清除所有字段的验证信息。
 * @param props
 */
function clearValidate(props?: string | string[]) {
  jsonFormRef.value?.clearValidate(props)
}
/**
 * 对部分表单字段进行校验的方法
 */
function validateField(
  props: string | string[],
  callback: ValidateFieldCallback
) {
  jsonFormRef.value?.validateField(props, callback)
}

const customErrorMap = reactive(new Map<string, string>())
function fitCustomErrorMsg(fieldProp: string, errorMsg: string) {
  customErrorMap.set(fieldProp, '')
  nextTick(() => customErrorMap.set(fieldProp, errorMsg))
}

/**
 * 判断表单项是显示还是隐藏
 * @param item
 * @param formData
 */
function itemHide(item: JsonFormItemType, formData: any) {
  return item.hide ? item.hide(item, formData) : false
}

defineExpose({
  getElFormInstance,
  validate,
  resetFields,
  validateField,
  fitCustomErrorMsg,
  clearValidate,
})
</script>

<template>
  <el-form
    ref="jsonFormRef"
    v-bind="elFormProp"
    :model="formData"
    v-on="elFormEvent"
  >
    <template v-for="item in itemArr" :key="item.fieldProp">
      <el-form-item
        v-if="!itemHide(item, formData)"
        v-bind="item.elFormItemProp"
        ref="elFormItemRef"
        :label="item.labelName"
        :prop="item.fieldProp"
        :class="item.class ? item.class : ''"
        :error="customErrorMap.get(item.fieldProp)"
        :rules="item.rules"
      >
        <component
          :is="item.component"
          v-bind="extractCustomProp(item)"
          :item="item"
          :form-data="formData"
          v-on="extractCustomEvent(item)"
        ></component>
      </el-form-item>
    </template>
  </el-form>
</template>

<style lang="scss" scoped></style>
